#include "stdafx.h"
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <string>
#include <fcntl.h>
#include <io.h>
#include "resource.h"

#include <Radiosity\Radiosity.h>
#include <Radiosity\Parameters.h>
#include <Utilities\Camera.h>
#include <Utilities\Exceptions.h>
#include "RadiosityTimer.h"

#define MAX_LOADSTRING 100
#define COLOR_DEPTH	32

enum CameraView
{
	User,
	ForceFov
};

void ReportError(std::string err);
void InitializeCameras(void);
void InitializeRadiosity(void);
GLvoid Resize(GLsizei width, GLsizei height);
void Display(GLvoid);
BOOL CreateDisplay(int width, int height);
GLvoid DestroyDisplay(GLvoid);
bool RestartSystem(void);
ATOM MyRegisterClass(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

bool StartProgram(void);
void RunProgram(void);

HDC hDC = NULL;
HGLRC hRC = NULL;
HWND hWnd = NULL;
HINSTANCE hInstance;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
bool keys[256];
CString msg;
CString cap;

static Camera __cameras[2];
static CameraView	__cameraMode;
static int __selectedCamera = 0;
static Parameters* __parameters;
static Radiosity* __radiosity;
static bool __iterate = true;
static bool __paused = false;
static bool __done = false;
static bool __active = true;
static bool __mouseDown = false;
static float __userFov = 45.0f;
static GLsizei __width, __height;

static RadiosityTimer *__timer;


GLvoid Resize(GLsizei width, GLsizei height)
{
	if(height == 0)
	{
		height = 1;
	}
	__height = height;
	__width = width;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity(); 
}

void InitializeCameras(void)
{
	for(int i = 0; i < 2; i++)
	{
		__cameras[i].Reset();
		__cameras[i].SetPosition(Vector3d(0, 0, -100));
		__cameras[i].SetAim(Vector3d(0, 0, 0));
		__cameras[i].SetUp(Vector3d(0, 1, 0));
		__cameras[i].SetClippingPlanes(0.05f, 1000.0f);
		__cameras[i].SetFov(__userFov);
		__cameras[i].SetDefaults();
		__cameras[i].SetPosition(__parameters->GetCameraPosition(i));
		__cameras[i].SetAim(__parameters->GetCameraAim(i));
		__cameras[i].SetUp(__parameters->GetCameraUp(i));
	}
}

void InitializeRadiosity(void)
{
	if(__parameters->GetRadiosityAlgorithm() == ProgressiveRefinement)
	{
		__radiosity = new Radiosity(__parameters, NULL);
	}
	else
	{
		__radiosity = new Radiosity(__parameters, &__cameras[0]);
	}

	__timer = new RadiosityTimer(DWORD(1000.0 / __parameters->GetRequestedFPS()), DWORD(__parameters->GetFovDecayInterval() * 1000));
}

void DisplayRadiosityMesh(int camera, GLint viewportX, int viewportY, GLsizei viewportWidth, GLsizei viewportHeight, int windowWidth, int windowHeight, float fov)
{
	glPushAttrib(GL_TRANSFORM_BIT);
	glPushAttrib(GL_VIEWPORT_BIT);
	glPushAttrib(GL_ENABLE_BIT);
	{
		glEnable(GL_SCISSOR_TEST);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		{
			glScissor(viewportX, viewportY, viewportWidth, viewportHeight);
			glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
			__cameras[camera].PerspectiveDisplayUpdate(windowWidth, windowHeight, fov);
			__radiosity->Display();
		}
		glPopMatrix();
	}
	glPopAttrib();
	glPopAttrib();
	glPopAttrib();
}

void Display(GLvoid)
{
	if(__parameters->GetDualView())
	{
		DisplayRadiosityMesh(0, 0, 0, __width / 2, __height, __parameters->GetWindowWidth() / 2, __parameters->GetWindowHeight(), (float)__parameters->GetFovAngle() );
		DisplayRadiosityMesh(1, __width / 2, 0, __width / 2, __height, __parameters->GetWindowWidth() / 2, __parameters->GetWindowHeight(), __userFov);
	}
	else
	{
		DisplayRadiosityMesh(0, 0, 0, __width, __height, __parameters->GetWindowWidth(), __parameters->GetWindowHeight(), __cameraMode == User ? __userFov : __radiosity->GetCurrentFov());
	}
	glFlush();
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_ACTIVATE:
		{
			__active = !HIWORD(wParam);
			return 0;
		}

	case WM_SYSCOMMAND:
		{
			switch (wParam)
			{
			case SC_SCREENSAVE:
			case SC_MONITORPOWER:
				{
					return 0;
				}
			}
			break;
		}

	case WM_CLOSE:
		{
			PostQuitMessage(EXIT_SUCCESS);
			return 0;
		}

	case WM_KEYDOWN:
		{
			keys[wParam] = TRUE;
			return 0;
		}

	case WM_KEYUP:
		{
			keys[wParam] = FALSE;
			return 0;
		}

	case WM_SIZE:
		{
			Resize(LOWORD(lParam), HIWORD(lParam));
			return 0;
		}

	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		{
			if(__parameters->GetDualView())
			{
				int x = LOWORD(lParam);
				__selectedCamera = (x < __width / 2) ? 0 : 1;
			}

			__mouseDown = true;
			__cameras[__selectedCamera].HandleMouseEvent(uMsg, LOWORD(lParam), HIWORD(lParam));
			return 0;
		}
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		{
			__mouseDown = false;
			__cameras[__selectedCamera].HandleMouseEvent(uMsg, LOWORD(lParam), HIWORD(lParam));
			return 0;
		}
	case WM_MOUSEMOVE:
		{
			__cameras[__selectedCamera].HandleMouseMotion(LOWORD(lParam), HIWORD(lParam));
			if(__mouseDown && (__parameters->GetRadiosityAlgorithm() != ProgressiveRefinement))
			{
				__radiosity->ChangeView();
			}
			return 0;
		}
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

GLvoid DestroyDisplay(GLvoid)
{
	if (__parameters->GetDisplayFullscreen())
	{
		ChangeDisplaySettings(NULL,0);
		ShowCursor(TRUE);
	}

	if (hRC)
	{
		if (!wglMakeCurrent(NULL,NULL))
		{
			msg.LoadString(IDS_RC_DC_RELEASE);
			cap.LoadString(IDS_ERROR);
			MessageBox(NULL, msg, cap, MB_OK | MB_ICONINFORMATION);
		}

		if (!wglDeleteContext(hRC))
		{
			msg.LoadString(IDS_RC_DELETE);
			cap.LoadString(IDS_ERROR);
			MessageBox(NULL, msg, cap, MB_OK | MB_ICONINFORMATION);
		}
		hRC = NULL;
	}

	if (hDC && !ReleaseDC(hWnd,hDC))
	{
		msg.LoadString(IDS_DC_RELEASE);
		cap.LoadString(IDS_ERROR);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONINFORMATION);
		hDC = NULL;
	}

	if (hWnd && !DestroyWindow(hWnd))
	{
		msg.LoadString(IDS_WND_DELETE);
		cap.LoadString(IDS_ERROR);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONINFORMATION);
		hWnd = NULL;
	}

	if (!UnregisterClass((LPCTSTR)szWindowClass, hInstance))
	{
		msg.LoadString(IDS_UNREGISTER_CLASS);
		cap.LoadString(IDS_ERROR);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONINFORMATION);
		hInstance = NULL;
	}
}

void OpenGLMainLoop(Parameters* const parameters)
{
	__parameters = parameters;
	__done = false;
	__iterate = true;
	__paused = true;
	__cameraMode = User;

	MSG msg;
	HACCEL hAccelTable;
	hInstance = GetModuleHandle(NULL);

	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_FOVDRIVENRADIOSITY, szWindowClass, MAX_LOADSTRING);

	if (!CreateDisplay(__parameters->GetWindowWidth(), __parameters->GetWindowHeight()))
	{
		return;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FOVDRIVENRADIOSITY));

	while(!__done)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				__done = true;
			}
			else if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else
		{
			if (__active)
			{
				if (keys[VK_ESCAPE])
				{
					__done = true;
				}
				else
				{
					RunProgram();
				}
			}

			if (keys[VK_F4])
			{
				keys[VK_F4] = FALSE;
				DestroyDisplay();
				__parameters->SetDisplayFullscreen(!__parameters->GetDisplayFullscreen());

				BOOL success = CreateDisplay(__parameters->GetWindowWidth(), __parameters->GetWindowHeight());
				if(!success)
				{
					return;
				}
			}

			if(keys[' '])
			{
				keys[' '] = FALSE;
				glFinish();
				__paused = !__paused;
			}

			if(keys[VK_UP])
			{
				keys[VK_UP] = FALSE;
				__cameraMode = ForceFov;
			}

			if(keys[VK_DOWN])
			{
				keys[VK_DOWN] = FALSE;
				__cameraMode = User;
			}

			if(keys['r'] || keys['R'])
			{
				keys['r'] = keys['R'] = false;
				for(int i = 0; i < 2; i++)
				{
					__cameras[i].Reset();
				}
			}
		}
	}

	delete __timer;
	delete __radiosity;
	DestroyDisplay();
	
	for(int i = 0; i < 2; i++)
	{
		__parameters->SetCameraPosition(i, __cameras[i].GetPosition());
		__parameters->SetCameraAim(i, __cameras[i].GetAim());
		__parameters->SetCameraUp(i, __cameras[i].GetUp());
	}

	return;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW |CS_OWNDC;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= NULL;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= NULL;
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= NULL;

	return RegisterClassEx(&wcex);
}

BOOL CreateDisplay(int width, int height)
{
	GLuint		PixelFormat;
	DWORD		dwExStyle;
	DWORD		dwStyle;
	RECT		WindowRect;
	WindowRect.left = (long)0;
	WindowRect.right = (long)width;
	WindowRect.top = (long)0;
	WindowRect.bottom = (long)height;

	hInstance = GetModuleHandle(NULL);

	if (!MyRegisterClass(hInstance))
	{
		msg.LoadString(IDS_REGISTER_CLASS);
		cap.LoadString(IDS_ERROR);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	if (__parameters->GetDisplayFullscreen())
	{
		DEVMODE dmScreenSettings;
		memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
		dmScreenSettings.dmSize = sizeof(dmScreenSettings);
		dmScreenSettings.dmPelsWidth = 800;
		dmScreenSettings.dmPelsHeight = 600;
		dmScreenSettings.dmBitsPerPel = COLOR_DEPTH;
		dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		{
			msg.LoadString(IDS_FULLSCREEN);
			cap.LoadString(IDS_ERROR);
			if (MessageBox(NULL, msg, cap, MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
			{
				__parameters->SetDisplayFullscreen(FALSE);
			}
			else
			{
				msg.LoadString(IDS_CLOSE);
				cap.LoadString(IDS_ERROR);
				MessageBox(NULL, msg, cap, MB_OK | MB_ICONSTOP);
				return FALSE;
			}
		}
	}

	if (__parameters->GetDisplayFullscreen())
	{
		dwExStyle = WS_EX_APPWINDOW;
		dwStyle = WS_POPUP;
		ShowCursor(FALSE);
	}
	else
	{
		dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
		dwStyle = WS_OVERLAPPEDWINDOW;
	}

	AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);

	hWnd = CreateWindowEx(dwExStyle, szWindowClass, szTitle, dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, -7, 0, WindowRect.right-WindowRect.left, WindowRect.bottom-WindowRect.top, NULL, NULL, hInstance, NULL);
	if (!hWnd)
	{
		DestroyDisplay();
		msg.LoadString(IDS_WINDOW_CREATE);
		cap.LoadString(IDS_ERROR);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	static	PIXELFORMATDESCRIPTOR pfd =	
	{
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		COLOR_DEPTH,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		COLOR_DEPTH,
		0, 0,
		PFD_MAIN_PLANE,
		0, 0, 0, 0
	};

	cap.LoadString(IDS_ERROR);
	hDC = GetDC(hWnd);
	if (!hDC)
	{
		DestroyDisplay();
		msg.LoadString(IDS_CREATE_DC);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	PixelFormat = ChoosePixelFormat(hDC, &pfd);
	if (!PixelFormat)
	{
		DestroyDisplay();
		msg.LoadString(IDS_CHOOSE_PIXEL_FORMAT);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	if(!SetPixelFormat(hDC, PixelFormat, &pfd))
	{
		DestroyDisplay();
		msg.LoadString(IDS_SET_PIXEL_FORMAT);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	hRC = wglCreateContext(hDC);
	if (!hRC)
	{
		DestroyDisplay();
		msg.LoadString(IDS_CREATE_RC);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	if(!wglMakeCurrent(hDC, hRC))
	{
		DestroyDisplay();
		msg.LoadString(IDS_ACTIVATE_RC);
		MessageBox(NULL, msg, cap, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}

	if(!StartProgram())
	{
		return FALSE;
	}

	ShowWindow(hWnd, SW_SHOW);
	SetForegroundWindow(hWnd);
	SetFocus(hWnd);
	glFlush();
	SwapBuffers(hDC);
	Resize(width, height);

	return TRUE;
}

void ReportError(std::string err)
{
	cap.LoadString(IDS_ERROR);
	MessageBox(NULL, CString(err.c_str()), cap, MB_OK | MB_ICONEXCLAMATION);
}

void RedirectIOToConsole()
{
	// allocate a console for this app
	AllocConsole();

	// set the screen buffer to be big enough to let us scroll text
	CONSOLE_SCREEN_BUFFER_INFO coninfo;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
	coninfo.dwSize.Y = 50;
	SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);

	// redirect unbuffered STDOUT to the console
	long lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
	int hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
	FILE *fp = _fdopen( hConHandle, "w" );
	*stdout = *fp;
	setvbuf( stdout, NULL, _IONBF, 0 );

	// redirect unbuffered STDIN to the console
	lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
	fp = _fdopen( hConHandle, "r" );
	*stdin = *fp;
	setvbuf( stdin, NULL, _IONBF, 0 );

	// redirect unbuffered STDERR to the console
	lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
	fp = _fdopen( hConHandle, "w" );
	*stderr = *fp;
	setvbuf( stderr, NULL, _IONBF, 0 );

	// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog 
	// point to console as well
	ios::sync_with_stdio();
}

bool StartProgram(void)
{
	try
	{
		InitializeCameras();
		InitializeRadiosity();
#ifdef _DEBUG
		RedirectIOToConsole();
#endif
	}
	catch(GraphicsCapabilityException &err)
	{
		DestroyDisplay();
		ReportError(std::string("Graphics capability error: ") + err.what());
		return FALSE;
	}
	catch(FileException &err)
	{
		DestroyDisplay();
		ReportError(std::string("File input error: ") + err.what());
		return FALSE;
	}
	catch (ShaderProgramCompilationException &err)
	{
		DestroyDisplay();
		ReportError(std::string("Shader program compilation error: ") + err.what());
		return FALSE;
	}
	catch (ShaderProgramException &err)
	{
		DestroyDisplay();
		ReportError(std::string("Shader program error: ") + err.what());
		return FALSE;
	}
	catch (exception &err)
	{
		DestroyDisplay();
		ReportError(std::string("Error while initializing: ") + err.what());
		return FALSE;
	}
	return TRUE;
}

void RunProgram(void)
{
	try
	{
		if(__iterate && !__paused)
		{
			__iterate = __radiosity->Iterate();
		}
		Display();
		SwapBuffers(hDC);
	}
	catch (ShaderProgramException &err)
	{
		ReportError(std::string("Shader program error: ") + err.what());
		__done = true;
	}
	catch(GraphicsExecutionException &err)
	{
		ReportError(std::string("Graphics execution error: ") + err.what());
		__done = true;
	}
	catch(...)
	{
		ReportError(std::string("Unknown error while executing program."));
		__done = true;
	}
}


